package com.flyqiu.flow.core;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.flyqiu.common.tools.ConditionTools;
import com.flyqiu.common.tools.StringTools;
import com.flyqiu.flow.api.element.FlyQiuFlowElementManage;
import com.flyqiu.flow.bpmn.FlyFlowElementConstants;
import com.flyqiu.flow.bpmn.FlyQiuBpmModeler;
import com.flyqiu.flow.bpmn.base.BaseProcElementNode;
import com.flyqiu.flow.bpmn.element.*;


import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class FlyQiuModelConvert {

    public static FlyQiuBpmModeler convert(FlyQiuFlowElementManage elementManage, byte[] source) {
        String json = new String(source, StandardCharsets.UTF_8);
        JSONObject jsonObject = JSONObject.parseObject(json);
        return convert(elementManage, jsonObject);
    }

    public static FlyQiuBpmModeler convert(FlyQiuFlowElementManage elementManage, JSONObject modelConfig) {
        JSONArray process = modelConfig.getJSONArray("process");
        String mainDefKey = modelConfig.getString("defKey");
        String mainDefName = modelConfig.getString("defName");
        if (StringTools.isEmpty(mainDefKey) && process.isEmpty()) {
            throw new RuntimeException("not find defKey");
        }
        if (StringTools.isEmpty(mainDefName) && process.isEmpty()) {
            throw new RuntimeException("not find defName");
        }

        List<ProcessModel> processModels = new ArrayList<>();
        int size = process.size();
        FlyQiuBpmModeler modeler = new FlyQiuBpmModeler();
        modeler.setBpmn(modelConfig);
        for (int i = 0; i < size; i++) {
            JSONObject processItem = process.getJSONObject(i);
            ProcessModel processModel = new ProcessModel();
            String defKey = processItem.getString("defKey");
            String defName = processItem.getString("defName");
            processModel.setDefKey(defKey);
            processModel.setDefName(defName);
            processModels.add(processModel);
            analysisProcessModel(processModels, elementManage, processItem, processModel);
            processModel.unLock();
        }
        if (StringTools.isEmpty(mainDefKey)) {
            ProcessModel processModel = processModels.get(0);
            mainDefKey = processModel.getDefKey();
        }
        modeler.setDefKey(mainDefKey);
        if (StringTools.isEmpty(mainDefName)) {
            ProcessModel processModel = processModels.get(0);
            mainDefName = processModel.getDefName();
        }


        modeler.setDefName(mainDefName);
        modeler.setProcessModels(processModels);
        modeler.setDiagrams(modelConfig.getJSONArray("diagram"));

        return modeler;

    }


    public static void analysisProcessModel(List<ProcessModel> processModels, FlyQiuFlowElementManage elementConfig, JSONObject processObject, ProcessModel model) {
        List<BaseProcElement> elements = new ArrayList<>();
        JSONArray activitys = processObject.getJSONArray("activity");
        int size = activitys.size();
        for (int i = 0; i < size; i++) {
            JSONObject activity = activitys.getJSONObject(i);
            String elementType = activity.getString("elementType");
            if (StringTools.isEmpty(elementType)) {
                continue;
            }
            if (elementType.equals("incoming")) {
                continue;
            }
            if (elementType.equals("outgoing")) {
                continue;
            }
            BaseProcElement element = createElement(elementConfig, activity, model);
            if (element instanceof StartEvent) {
                model.setStartEvent((StartEvent) element);
            }
            if (element instanceof SubProcessModel subProcessModel) {
                analysisProcessModel(processModels, elementConfig, activity, subProcessModel);
                subProcessModel.setParentProcessModel(model);
                subProcessModel.unLock();
                processModels.add(subProcessModel);
            }
            elements.add(element);
        }
        JSONArray sequences = processObject.getJSONArray("sequences");
        int sequencesSize = sequences.size();
        List<SequenceFlow> tempSequences = new ArrayList<>();
        for (int i = 0; i < sequencesSize; i++) {
            JSONObject activity = sequences.getJSONObject(i);
            BaseProcElement element = createElement(elementConfig, FlyFlowElementConstants.ELEMENT_TYPE_SEQUENCE_FLOW, activity, model);
            if (!(element instanceof SequenceFlow sequenceFlow)) {
                continue;
            }
            tempSequences.add(sequenceFlow);
        }
        elements.addAll(tempSequences);

        Map<String, BaseProcElement> elementMap = elements.stream().collect(Collectors.toMap(BaseProcElement::getDefKey, e -> e));
        model.setElementMap(elementMap);
        model.setElements(elements);
        Optional<BaseProcElement> firstStartEvent = elements.stream().filter(e -> e instanceof StartEvent).findFirst();
        if (firstStartEvent.isEmpty()) {
            throw new RuntimeException(model.getDefKey() + " in the process model does not include a startup event");
        }
        model.setStartEvent((StartEvent) firstStartEvent.get());
        for (BaseProcElement element : elements) {
            if (element instanceof SequenceFlow) {
                handlerSequenceFlow((SequenceFlow) element, elementMap);
            } else if (element instanceof BaseProcElementNode) {
                handlerProcNodeModelSequence((BaseProcElementNode) element, tempSequences);
            }
        }


    }

    private static void handlerProcNodeModelSequence(BaseProcElementNode element, List<SequenceFlow> tempSequences) {
        List<SequenceFlow> targetSequences = tempSequences.stream().filter(e -> ConditionTools.eq(e.getSourceDefKey(), element.getDefKey())).collect(Collectors.toList());
        List<SequenceFlow> sourceSequences = tempSequences.stream().filter(e -> ConditionTools.eq(e.getTargetDefKey(), element.getDefKey())).collect(Collectors.toList());
        element.setIngoingFlows(sourceSequences);
        element.setOutgoingFlows(targetSequences);
    }

    private static void handlerSequenceFlow(SequenceFlow sequenceFlow, Map<String, BaseProcElement> elementMap) {
        BaseProcElement srcNode = elementMap.get(sequenceFlow.getSourceDefKey());
        if (srcNode != null) {
            sequenceFlow.setSourceNode(srcNode);
        }

        BaseProcElement tagNode = elementMap.get(sequenceFlow.getTargetDefKey());
        if (tagNode != null) {
            sequenceFlow.setTargetNode(tagNode);
        }


    }

    private static BaseProcElement createElement(FlyQiuFlowElementManage elementConfig, JSONObject element, ProcessModel processModel) {
        String elementType = element.getString("elementType");
        return createElement(elementConfig, elementType, element, processModel);
    }

    private static BaseProcElement createElement(FlyQiuFlowElementManage elementConfig, String elementType, JSONObject element, ProcessModel processModel) {
        BaseProcElement elementInst = elementConfig.createElement(elementType);
        elementInst.setDefKey(element.getString("defKey"));
        elementInst.setDefName(element.getString("defName"));
        elementInst.init(element, processModel);
        return elementInst;
    }


}

